#include "Engine.h"

namespace TSS
{

	Engine::Engine()
	{
		p_appTitle = "TSS Engine";
		p_screenWidth = 1024;
		p_screenHeight = 768;
		p_colorDepth = 32;
		p_fullscreen = false;
		p_device = NULL;
		p_coreFrameCount = 0;
		p_coreFrameRate = 0;
		p_screenFrameCount = 0;
		p_screenFrameRate = 0;
		p_backdropColor = D3DCOLOR_XRGB(0, 0, 80);
		p_hWnd = 0;
		p_pauseMode = false;
		p_versionMajor = VERSION_MAJOR;
		p_versionMinor = VERSION_MINOR;
		p_revision = REVISION;
		p_commandLineParams = "";

		p_MainSurface = 0;
		p_MainDepthStencilSurface = 0;
	}

	Engine::~Engine()
	{
		delete p_input;
		if(p_device)
			p_device->Release();
		if(p_d3d)
			p_d3d->Release();
	}

	void Engine::Shutdown()
	{
		PostQuitMessage(0);
	}

	void Engine::raiseEvent(IEvent* e)
	{
		Game_Event(e);
		delete e;
	}

	void Engine::updateKeyboard()
	{
		static char old_keys[256];


		for(int i = 0; i < 255; i++)
		{
			//key press
			if(p_input->GetKeyState(i) & 0x80)
			{
				old_keys[i] = p_input->GetKeyState(i);
				raiseEvent(new KeyPressEvent(i));
			}
			//key release
			else if(old_keys[i] & 0x80)
			{
				old_keys[i] = p_input->GetKeyState(i);
				raiseEvent(new KeyReleaseEvent(i));
			}
		}
	}

	void Engine::updateMouse()
	{
		static int oldPosX = 0;
		static int oldPosY = 0;

		//mouse buttons
		for(int i = 0; i < 4; i++)
		{
			if(p_input->GetMouseButton(i))
			{
				raiseEvent(new MouseClickEvent(i));
			}
		}

		int posx = p_input->GetMousePosX();
		int posy = p_input->GetMousePosY();

		if(posx != oldPosX || posy != oldPosY)
		{
			oldPosX = posx;
			oldPosY = posy;

			raiseEvent(new MouseMoveEvent(posx, posy));
		}

		int deltax = p_input->GetMouseDeltaX();
		int deltay = p_input->GetMouseDeltaY();
		if(deltax != 0 || deltay != 0)
		{
			raiseEvent(new MouseMotionEvent(deltax, deltay));
		}

		int wheel = p_input->GetMouseDeltaWheel();
		if(wheel != 0)
		{
			raiseEvent(new MouseWheelEvent(wheel));
		}
	}

	void Engine::Update(double deltaTime)
	{
		static double accumTime = 0;

		p_coreFrameCount++;
		if(p_coreTimer.StopWatch(1000))
		{
			p_coreFrameRate = p_coreFrameCount;
			p_coreFrameCount = 0;
		}

		//fast update
		Game_Update(deltaTime);

		//60 fps = 16ms per frame
		if(!timedUpdate.StopWatch(16))
		{
			timedUpdate.Rest(1);
		}
		else
		{
			//real fps
			p_screenFrameCount++;
			if(p_screenTimer.StopWatch(1000))
			{
				p_screenFrameRate = p_screenFrameCount;
				p_screenFrameCount = 0;
			}

			//input update
			p_input->Update();
			updateKeyboard();
			updateMouse();

			//rendering
			if(p_device->BeginScene() == D3D_OK)
			{
				g_engine->ClearScene(p_backdropColor);

				Game_Render3d();

				//2d rendering
				p_sprite->Begin(D3DXSPRITE_ALPHABLEND);
				Game_Render2d();
				p_sprite->End();

				p_device->EndScene();
				p_device->Present(0, 0, 0, 0);
			}
		}
	}

	void Engine::ClearScene(D3DCOLOR color)
	{
		p_device->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, color, 1.0f, 0);
	}

	void Engine::RestorePrimaryRenderTarget()
	{
		p_device->SetRenderTarget(0, p_MainSurface);
		p_device->SetDepthStencilSurface(p_MainDepthStencilSurface);
	}

	void Engine::SavePrimaryRenderTarget()
	{
		p_device->GetRenderTarget(0, &p_MainSurface);
		p_device->GetDepthStencilSurface(&p_MainDepthStencilSurface);
	}

	void Engine::SetIdentity()
	{
		D3DXMATRIX ident;
		D3DXMatrixIdentity(&ident);
		g_engine->getDevice()->SetTransform(D3DTS_WORLD, &ident);
	}

	void Engine::SetSpriteIdentity()
	{
		D3DXMATRIX ident;
		D3DXMatrixIdentity(&ident);
		g_engine->getSpriteObj()->SetTransform(&ident);
	}

	bool Engine::Init(HINSTANCE hInstance, int width, int height, int colorDepth, bool fullscreen)
	{
		std::string title;

		title = g_engine->getAppTitle();

		//window dimensions
		RECT windowRect;
		windowRect.left = 0;
		windowRect.right = g_engine->getScreenWidth();
		windowRect.top = 0;
		windowRect.bottom = g_engine->getScreenHeight();

		WNDCLASSEX wc;
		memset(&wc, 0, sizeof(wc));
		wc.cbSize = sizeof(wc);
		wc.style = CS_HREDRAW | CS_VREDRAW;
		wc.lpfnWndProc = (WNDPROC) WndProc;
		wc.hInstance = hInstance;
		wc.lpszClassName = title.c_str();
		wc.hCursor = LoadCursor(NULL, IDC_ARROW);
		wc.cbClsExtra = 0;
		wc.cbWndExtra = 0;
		wc.hIcon = 0;
		wc.hIconSm = 0;
		wc.hbrBackground = 0;
		wc.lpszMenuName = 0;

		RegisterClassEx(&wc);

		DWORD dwStyle, dwExStyle;
		if(g_engine->getFullscreen())
		{
			DEVMODE dm;
			memset(&dm, 0, sizeof(dm));
			dm.dmSize = sizeof(dm);
			dm.dmPelsWidth = g_engine->getScreenWidth();
			dm.dmPelsHeight = g_engine->getScreenHeight();
			dm.dmBitsPerPel = g_engine->getColorDepth();
			dm.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;

			if(ChangeDisplaySettings(&dm, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL)
			{
				debug << "Display mode change failed" << std::endl;
				g_engine->setFullscreen(false);
			}
			dwStyle = WS_POPUP;
			dwExStyle = WS_EX_APPWINDOW;
		}
		else
		{
			dwStyle = WS_OVERLAPPEDWINDOW | WS_VISIBLE;
			dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
		}

		//make display area real size
		AdjustWindowRectEx(&windowRect, dwStyle, FALSE, dwExStyle);
		int wwidth = windowRect.right - windowRect.left;
		int wheight = windowRect.bottom - windowRect.top;

		debug << "Screen size: " << width << ", " << height << std::endl;
		debug << "Creating program window" << std::endl;

		HWND hWnd = CreateWindowEx(0, title.c_str(), title.c_str(), dwStyle | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, 0, 0, wwidth, wheight, 0, 0, hInstance, 0);


		//check for error making window
		if(!hWnd)
		{
			debug << "Error creating program window" << std::endl;
			return false;
		}

		//display window
		ShowWindow(hWnd, SW_SHOW);
		UpdateWindow(hWnd);

		g_engine->setWindowHandle(hWnd);

		//setting up Direct3D
		debug << "Setting up Direct3D" << std::endl;

		p_d3d = Direct3DCreate9(D3D_SDK_VERSION);
		if(p_d3d == NULL)
		{
			return false;
		}

		//get system color depth
		D3DDISPLAYMODE dm;
		p_d3d->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &dm);

		//setup options D3D
		D3DPRESENT_PARAMETERS d3dpp;
		ZeroMemory(&d3dpp, sizeof(d3dpp));
		d3dpp.Windowed = (!fullscreen);
		d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
		d3dpp.EnableAutoDepthStencil = 1;
		d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8;
		d3dpp.Flags = D3DPRESENTFLAG_DISCARD_DEPTHSTENCIL;
		d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
		d3dpp.BackBufferFormat = dm.Format;
		d3dpp.BackBufferCount = 1;
		d3dpp.BackBufferWidth = width;
		d3dpp.BackBufferHeight = height;
		d3dpp.hDeviceWindow = p_hWnd;
		d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE;

		debug << "Creating D3D Device" << std::endl;

		p_d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, p_hWnd, D3DCREATE_HARDWARE_VERTEXPROCESSING, &d3dpp, &p_device);

		//hardware failed try software
		if(p_device == NULL)
		{
			debug << "Hardware vertex option failed! Trying Software..." << std::endl;
			p_d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, p_hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &p_device);

			if(p_device == NULL)
			{
				debug << "Software option failed, shutting down!" << std::endl;
				return false;
			}
			else
			{
				debug << "Using Software processing" << std::endl;
			}
		}

		debug << "Setting up 2d rendering" << std::endl;

		//setup 2d rendering
		HRESULT reslt = D3DXCreateSprite(p_device, &p_sprite);
		if(reslt != D3D_OK)
		{
			debug << "D3DXCreateSprite Failed!" << std::endl;
			return false;
		}

		//input setup
		debug << "Setting up Input" << std::endl;

		p_input = new Input(getWindowHandle());

		debug << "Calling game_init(" << getWindowHandle() << ")" << std::endl;

		//call game init
		if(!Game_Init(getWindowHandle()))
			return false;

		debug << "Engine init completed!" << std::endl;

		return true;
	}

	void Engine::setScreen(int w, int h, int d, bool full)
	{
		setScreenWidth(w);
		setScreenHeight(h);
		setColorDepth(d);
		setFullscreen(full);
	}

	void Engine::Message(std::string message, std::string title)
	{
		MessageBox(0, message.c_str(), title.c_str(), 0);
	}

	std::string Engine::getVersionText()
	{
		std::ostringstream s;
		s << "TSS Engine v" << p_versionMajor << "." << p_versionMinor << "." << p_revision;
		return s.str();
	}

};